package com.nwpu.coursesupport.pagination;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;

import com.nwpu.coursesupport.pagination.Dialect;
import com.nwpu.coursesupport.pagination.DialectFactory;
import com.nwpu.coursesupport.pagination.DialectType;
import com.nwpu.coursesupport.pagination.PageBounds;
import com.nwpu.coursesupport.tool.StringUtil;


/**
 * @author Harry
 *
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PaginationInterceptor implements Interceptor {

	/**
	 * Logger instance for logging
	 */
	private static final Log LOGGER = LogFactory.getLog(PaginationInterceptor.class);

	/**
	 * Default dialect name
	 */
	private static final String DEFAULT_DIALECT_NAME = "MYSQL";

	/**
	 * Dialect name
	 */
	private String dialectName;

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		if (StringUtil.isEmpty(dialectName)) {
			dialectName = DEFAULT_DIALECT_NAME;
			LOGGER.info("Property dialectName isn't set, using default " + dialectName + " dialect");
		}
		DialectType dialectType = DialectType.getDialectType(dialectName);
		if (dialectType == null) {
			LOGGER.error("Cannot find " + dialectName + " dialect type");
			return invocation.proceed();
		}
		Dialect dialect = DialectFactory.getDialect(dialectType);
		if (dialect == null) {
			LOGGER.error("Cannot find " + dialectName + " dialect");
			return invocation.proceed();
		}
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
		RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
		PageBounds pageBounds = null;
		if (rowBounds instanceof PageBounds) {
			pageBounds = (PageBounds) rowBounds;
		}
		if (pageBounds != null && pageBounds.getSize() > 0 && pageBounds.getNumber() > 0) {
			MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
			BoundSql boundSql = statementHandler.getBoundSql();
			Object parameterObject = boundSql.getParameterObject();
			Connection connection = (Connection) invocation.getArgs()[0];
			int count = getCount(metaStatementHandler, rowBounds, mappedStatement, connection, parameterObject, dialect);
			pageBounds.setCount(count);
			pageBounds.setTotal(count / pageBounds.getSize() + (count % pageBounds.getSize() > 0 ? 1 : 0));
			metaStatementHandler.setValue("delegate.boundSql.sql", dialect.getLimitSql(boundSql.getSql(), pageBounds.getSize() * (pageBounds.getNumber() - 1), pageBounds.getSize()));
			metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
			metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
		}
		return invocation.proceed();
	}

	/**
	 * 
	 * @param metaObject
	 * @param rowBound
	 * @param mappedStatement
	 * @param connection
	 * @param parameterObject
	 * @param dialect
	 * @return
	 */
	private int getCount(final MetaObject metaObject, final RowBounds rowBounds, final MappedStatement mappedStatement, final Connection connection, final Object parameterObject, Dialect dialect) {
		BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
		String countSql = dialect.getCountSql(boundSql.getSql());
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		int count = 0;
		try {
			preparedStatement = connection.prepareStatement(countSql);
			ParameterHandler handler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
			handler.setParameters(preparedStatement);
			resultSet = preparedStatement.executeQuery();
			if (resultSet.next()) {
				count = resultSet.getInt(1);
			}
		} catch (Exception e) {
			LOGGER.error(e);
		} finally {
			if (preparedStatement != null) {
				try {
					preparedStatement.close();
				} catch (SQLException e) {
					LOGGER.error(e);
				}
			}
		}
		return count;
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
	}

	public String getDialectName() {
		return dialectName;
	}

	public void setDialectName(String dialectName) {
		this.dialectName = dialectName;
	}
}

